package com.redstar.HappyRefresh.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.redstar.HappyRefresh.audit.VideoPublishAuditService;
import com.redstar.HappyRefresh.cache.UserInfo;
import com.redstar.HappyRefresh.config.QiNiuConfig;
import com.redstar.HappyRefresh.constant.AuditStatus;
import com.redstar.HappyRefresh.constant.RedisConstant;
import com.redstar.HappyRefresh.mapper.VideoMapper;
import com.redstar.HappyRefresh.pojo.*;
import com.redstar.HappyRefresh.service.*;
import com.redstar.HappyRefresh.util.JwtUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class VideoServiceImpl extends ServiceImpl<VideoMapper, Video> implements VideoService {

    @Autowired
    private TypeService typeService;
    @Autowired
    private FileService fileService;
    @Autowired
    private VideoPublishAuditService videoPublishAuditService;
    @Autowired
    private VideoStarService videoStarService;
    @Autowired
    private VideoShareService videoShareService;
    @Autowired
    private InterestPushService interestPushService;
    @Autowired
    private UserService userService;
    @Autowired
    private FavoritesService favoritesService;
    @Autowired
    private FavoritesVideoService favoritesVideoService;
    @Autowired
    private FollowService followService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private FeedService feedService;

    @Override
    public String publishVideo(Video video) { // 发布视频或修改视频
        Video oldVideo = null;
        Long userId = UserInfo.get();
        Long videoId = video.getId();
        if (videoId != null) { // videoId不为null，修改视频
            oldVideo = this.getOne(new LambdaQueryWrapper<Video>().eq(Video::getId, video).eq(Video::getUserId, userId));
            // url或cover不一致，不允许修改视频源
            if (!video.buildVideoUrl().equals(oldVideo.buildVideoUrl()) || !video.buildCoverUrl().equals(oldVideo.buildCoverUrl())) {
                return "不能更换视频源，只能修改视频信息";
            }
        }
        // 判断视频对应的分类是否存在
        Long typeId = video.getTypeId();
        if (typeId == null) {
            return "修改视频需要传递videoId";
        } else if (ObjectUtils.isEmpty(typeService.getById(typeId))) {
            return "视频对应的分类不存在";
        }
        // 判断视频标签是否合法
        int size = video.buildLabel().size();
        if (size == 0) {
            return "视频必须存在标签";
        }
        video.setAuditStatus(AuditStatus.PROCESS); // 审核中
        if (videoId == null) { // 发布视频
            video.setUserId(userId);
            // 视频标识，YV加随机字符串
            video.setYv("YV" + UUID.randomUUID().toString().replace("-", "").substring(8));
            // 封面为null，需要设置封面
            if (video.getCover() == null) {
                // 从视频文件中截取一帧作为封面
                video.setCover(fileService.generatePhoto(video.getUrl(), userId));
            }
        } else { // 修改视频，这些属性在oldVideo中都存在且不能修改
            video.setYv(null); // 视频标识
            video.setVideoType(null); // 文件的性质
            video.setLabelNames(null); // 视频标签
            video.setUrl(null); // 视频文件id
            video.setCover(null); // 视频封面id
        }
        // 填充视频时长
        if (videoId == null || !StringUtils.hasLength(oldVideo.getDuration())) {
            Long url = video.getUrl();
            if (url == null) { // url为null，说明是修改视频
                url = oldVideo.getUrl();
            }
            // 获取真正的视频url
            String fileUrl = fileService.getById(url).getFileKey();
            // 通过url获取视频时长
            String uuid = UUID.randomUUID().toString();
            String duration = fileService.getVideoDuration(QiNiuConfig.CNAME + "/" + fileUrl + "?uuid=" + uuid);
            video.setDuration(duration);
        }
        // 代理对象，在方法上的事务只有在使用代理对象调用时才会生效
        VideoServiceImpl videoService = (VideoServiceImpl) AopContext.currentProxy();
        // 插入或更新一条数据
        videoService.saveOrUpdate(video);
        VideoTask videoTask = new VideoTask(); // 封装发布视频，用于审核
        videoTask.setOldVideo(oldVideo);
        videoTask.setVideo(video);
        videoTask.setIsAdd(videoId == null);
        videoTask.setOldState(videoId == null || oldVideo.getOpen());
        videoTask.setNewState(video.getOpen());
        // 申请慢审核
        videoPublishAuditService.audit(videoTask, false);
        return "success";
    }

    @Override
    public String deleteVideo(Long id) { // 删除视频
        Video video = this.getById(id);
        if (!Objects.equals(video.getUserId(), UserInfo.get())) { // 用户只能删除自己的视频
            return "你小子还想删除别人的视频";
        }
        // 删除视频
        if (this.removeById(id)) {
            // 删除成功后异步删除该视频的相关信息，收藏信息不应该被删除，可以在收藏夹中显示已删除
            new Thread(() -> {
                // 删除该视频的点赞信息
                videoStarService.remove(new LambdaQueryWrapper<VideoStar>().eq(VideoStar::getVideoId, id));
                // 删除该视频的分享信息
                videoShareService.remove(new LambdaQueryWrapper<VideoShare>().eq(VideoShare::getVideoId, id));
                // 删除系统视频库中记录的该视频的id
                interestPushService.deleteSystemStockIn(video);
                // 删除系统分类库中记录的该视频的id
                interestPushService.deleteSystemTypeStockIn(video);
            }).start();
        }
        return "success";
    }

    @Override
    public IPage<Video> listByUserIdVideo(BasePage basePage, Long userId) { // 查看用户所管理的视频-稿件管理，分页
        IPage<Video> page = this.page(basePage.page(), new LambdaQueryWrapper<Video>().eq(Video::getUserId, userId).orderByDesc(Video::getGmtCreated));
        List<Video> videoList = page.getRecords();
        setUserVoAndVideoType(videoList);
        return page;
    }

    @Override
    public String starVideo(Long id, Long userId) { // 点赞视频或取消点赞
        Video video = this.getById(id);
        if (video == null) {
            return "false"; // 视频不存在
        }
        VideoStar videoStar = new VideoStar();
        videoStar.setVideoId(id);
        videoStar.setUserId(userId);
        // 对VideoStar表进行操作
        Boolean isStar = videoStarService.starVideo(videoStar);
        // 修改点赞数
        updateStar(videoStar.getVideoId(),isStar ? "+" : "-");
        // 获取该视频的全部标签
        List<String> buildLabel = video.buildLabel();
        // 为视频点赞，该视频的全部标签兴趣推送的概率加2
        UserModel userModel = UserModel.buildUserModel(buildLabel, id, isStar ? 2.0D : -2.0D);
        // 修改用户模型
        interestPushService.updateUserModel(userModel);
        return isStar ? "已点赞" : "取消点赞";
    }

    @Override
    @Async
    public void addWatchHistory(Long id, Long userId) { // 添加浏览记录，用于兴趣推送去重
        String key1 = RedisConstant.HISTORY_VIDEO + id + ":" + userId;
        String key2 = RedisConstant.USER_HISTORY_VIDEO + userId;
        Object value = redisTemplate.opsForValue().get(key1);
        if (value == null) { // 不需要重复添加，记录第一次即可
            // 将videoId存储在redis中，用于兴趣推送去重，有效期为5天
            redisTemplate.opsForValue().set(key1, id, RedisConstant.HISTORY_TIME);
            Video video = this.getById(id);
            // 设置用户的基本信息，该用户指的是视频发布者
            video.setUser(userService.getInfo(video.getUserId()));
            // 设置分类名称
            video.setTypeName(typeService.getById(video.getTypeId()).getName());
            // 将video存储在redis中，用户浏览记录，有效期为5天
            redisTemplate.opsForZSet().add(key2, video, new Date().getTime());
            redisTemplate.expire(key2, RedisConstant.HISTORY_TIME, TimeUnit.SECONDS);
            // 修改用户浏览量
            updateHistory(video.getId());
        }
    }

    @Override
    public HashMap<String, List<Video>> getHistory(BasePage basePage) { // 获取用户的浏览记录，分页查询，然后将视频按照分数降序排序，分数就是视频被浏览时的时间
        String key = RedisConstant.USER_HISTORY_VIDEO + UserInfo.get();
        Long pageNum = basePage.getPageNum();
        Long pageSize = basePage.getPageSize();
        Set<ZSetOperations.TypedTuple<Object>> typedTupleSet = null;
        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            long start = (pageNum - 1) * pageSize;
            long end = pageNum * pageSize - 1;
            Long size = redisTemplate.opsForZSet().size(key);
            if (size != null && end > size) {
                end = -1;
            }
            typedTupleSet =  redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
        }
        // 用于格式化时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        HashMap<String, List<Video>> videoMap = new HashMap<>();
        // 将所有的video记录下来统一设置UserVo和videoType
        ArrayList<Video> videoList = new ArrayList<>();
        for (ZSetOperations.TypedTuple<Object> typedTuple : typedTupleSet) {
            Double score = typedTuple.getScore();
            if (score == null) {
                // 正常情况下不会出现score为null的情况
                continue;
            }
            Date date = new Date(score.longValue());
            String format = simpleDateFormat.format(date);
            if (!videoMap.containsKey(format)) {
                videoMap.put(format, new ArrayList<>());
            }
            Video video = (Video)typedTuple.getValue();
            videoMap.get(format).add(video);
            videoList.add(video);
        }
        setUserVoAndVideoType(videoList);
        return videoMap;
    }

    @Override
    public List<Video> listVideoByFavorites(Long favoritesId) { // 获取收藏夹下的视频
        Favorites favorites = favoritesService.getById(favoritesId);
        if (ObjectUtils.isEmpty(favorites)) {
            throw new RuntimeException("收藏夹不存在"); // 收藏夹不存在
        }
        List<FavoritesVideo> favoritesVideoList = favoritesVideoService.list(new LambdaQueryWrapper<FavoritesVideo>().eq(FavoritesVideo::getFavoritesId, favoritesId));
        List<Long> videoIdList = favoritesVideoList.stream().filter(o -> !ObjectUtils.isEmpty(o)).map(FavoritesVideo::getVideoId).collect(Collectors.toList());
        List<Video> videoList = new ArrayList<>();
        if (!ObjectUtils.isEmpty(videoIdList)) {
            videoList = this.list(new LambdaQueryWrapper<Video>().in(Video::getId, videoIdList));
        }
        return videoList;
    }

    @Override
    public String favoritesVideo(Long fId, Long vId){ // 收藏视频/取消收藏
        Favorites favorites = favoritesService.getById(fId);
        if (favorites == null) {
            return "false"; // 收藏夹不存在
        }
        Video video = this.getById(vId);
        if (video == null) {
            return "false"; // 视频不存在
        }
        // 填充收藏视频信息
        FavoritesVideo favoritesVideo = new FavoritesVideo();
        favoritesVideo.setFavoritesId(fId);
        favoritesVideo.setVideoId(vId);
        favoritesVideo.setUserId(favorites.getUserId());
        // 对favorites_video表进行操作
        Boolean isFavorites = favoritesVideoService.favoritesVideo(favoritesVideo);
        updateFavorites(favoritesVideo.getVideoId(), isFavorites ? "+" : "-");
        List<String> buildLabel = video.buildLabel();
        UserModel userModel = UserModel.buildUserModel(buildLabel, vId, isFavorites ? 3.0D : -3.0D);
        // 修改用户模型
        interestPushService.updateUserModel(userModel);
        return isFavorites ? "已收藏" : "取消收藏";
    }

    @Override
    public List<Video> followFeed(Long userId, Long lastTime) { //  推送已关注用户发布的视频，拉模式，滚动分页
        // 从收件箱中获取视频的id，收件箱中的数据是通过拉模式从关注用户的发件箱中获取的
        String key1 = RedisConstant.IN_FOLLOW + userId;
        double min = 0D, max = lastTime == null ? new Date().getTime() : lastTime;
        String key2 = RedisConstant.SCROLL_PAGE + userId + ":" + lastTime;
        long offset = 1L; // 默认为1
        Object offsetObject = redisTemplate.opsForValue().get(key2);
        if (offsetObject != null) {
            offset = Long.parseLong(offsetObject.toString());
        }
        // 从收件箱中取出5条数据
        Set<Long> videoIdList = redisTemplate.opsForZSet().reverseRangeByScore(key1, min, max, offset, 5).stream().map(o -> Long.valueOf(o.toString())).collect(Collectors.toSet());
        List<Video> videoList = null;
        // 将视频按照发布时间从大到小排序
        if (!ObjectUtils.isEmpty(videoIdList)) {
            videoList = this.list(new LambdaQueryWrapper<Video>().in(Video::getId, videoIdList).orderByDesc(Video::getGmtCreated));
            // 判断偏移量
            int nextOffset = 1;
            for (int i = videoList.size() - 1; i >= 0; i--) {
                Video rightVideo = videoList.get(i);
                Video leftVideo = videoList.get(i - 1);
                if (rightVideo.getGmtCreated() == leftVideo.getGmtCreated()) {
                    nextOffset++;
                } else break;
            }
            // nextOffset是滚动分页下一次请求的偏移量
            String nextKey = RedisConstant.SCROLL_PAGE + userId + videoList.get(videoList.size() - 1).getGmtCreated();
            redisTemplate.opsForValue().set(nextKey, nextOffset);
            setUserVoAndVideoType(videoList); //  为Video对象设置UserVo和videoType
        }
        return videoList;
    }

    @Override
    public List<Video> pushVideos(Long userId) { // 兴趣推送视频
        User user = userService.getById(userId);;
        // 获取最多10条兴趣推送的视频的Id
        List<Long> videoIdList = interestPushService.listVideoIdByUserModel(user);
        List<Video> videoList;
        // 通过视频id查找视频
        if (ObjectUtils.isEmpty(videoIdList)) { // 兴趣推送失败，推送10条最新的视频
            videoList = this.list(new LambdaQueryWrapper<Video>().orderByDesc(Video::getGmtCreated).last("limit 10"));
        } else {
            videoList = this.listByIds(videoIdList);
        }
        setUserVoAndVideoType(videoList); // 为Video对象设置UserVo和videoType
        return videoList;
    }

    @Override
    public List<Video> searchVideo(String searchName, BasePage basePage, Long userId) { // 搜索视频
        // 搜索内容包含YV，代表精确搜索
        // 搜索内容可以为null，分页查询
        LambdaQueryWrapper<Video> queryWrapper = new LambdaQueryWrapper<>();
        // 只能查询到公开的视频
        String VIDEO_OPEN = "0";
        queryWrapper.eq(Video::getOpen, VIDEO_OPEN);
        if (searchName.contains("YV")) { // 精确搜索
            queryWrapper.eq(Video::getYv, searchName);
        } else {
            // 搜索内容不为null时，按视频标题查询
            queryWrapper.like(StringUtils.hasLength(searchName), Video::getTitle, searchName);
        }
        // 分页查询
        IPage<Video> videoPage = this.page(basePage.page(), queryWrapper);
        // 添加搜索记录
        if (userId == null) {
            userId = -1L; // 表示游客登录
        }
        if (!StringUtils.hasLength(searchName)) { // 表示查询所有
            searchName = "ALL";
        }
        // 添加用户的搜索记录
        redisTemplate.opsForZSet().add(RedisConstant.USER_SEARCH_HISTORY + userId,  searchName, new Date().getTime());
        redisTemplate.expire(RedisConstant.USER_SEARCH_HISTORY + userId, RedisConstant.USER_SEARCH_HISTORY_TIME, TimeUnit.SECONDS);
        // 返回分页查询出的视频
        return videoPage.getRecords();
    }

    @Override
    public List<Video> getVideoByTypeId(Long typeId) { // 根据分类随机推送视频
        // 判断typeId对应的分类是否存在
        Type type = typeService.getById(typeId);
        if (type == null) {
            throw new RuntimeException("getVideoByTypeId方法：typeId对应的分类不存在");
        }
        Set<Long> videoIdSet = interestPushService.listVideoIdByTypeId(typeId);
        List<Video> videoList = new ArrayList<>();
        if (!ObjectUtils.isEmpty(videoIdSet)) {
            videoList = this.listByIds(videoIdSet);
            setUserVoAndVideoType(videoList); // 为Video对象设置UserVo和videoType
        }
        return videoList;
    }

    @Override
    public String share(Long videoId, HttpServletRequest request) { // 分享视频
        // 主要是记录视频被分享的相关数据，分享视频在前端完成
        String ip = ""; //视频分享的标识
        if (request.getHeader("x-forwarded-for") == null) {
            ip = request.getRemoteAddr(); // 发送请求的客户端的ip地址的string
        } else {
            ip = request.getHeader("x-forwarded-for");
        }
        VideoShare videoShare = new VideoShare();
        videoShare.setVideoId(videoId);
        videoShare.setIp(ip);
        Long userId = JwtUtils.getUserId(request);
        if (userId != null) {
            videoShare.setUserId(userId);
        }
        Video video = this.getById(videoId);
        if (video == null) {
            return "无法找到id为" + videoId + "的视频";
        }
        // 保存视频发享记录，同一个用户可以分享多次，但只记录第一次分享
        Boolean saveResult = videoShareService.saveVideoShare(videoShare);
        if (saveResult) { // 修改当前视频的分享数，分享数加1
            this.update(new LambdaUpdateWrapper<Video>().setSql("share_count = share_count + 1").eq(Video::getId, videoId));
        }
        return "success";
    }

    @Override
    public Video getVideoById(Long videoId, Long userId) { // 根据videoId获取视频详情，是否点赞，是否收藏，是否关注
        Video video = this.getById(videoId);
        if (video == null) {
            throw new RuntimeException("getVideoById方法出错：无法找到id为" + videoId + "的视频");
        }
        if (video.getOpen()) {
            log.error("getVideoById方法出错：私密视频无法被查询");
            video = null;
        } else {
            setUserVoAndVideoType(Collections.singletonList(video)); // 将video封装成list，为video设置userVo和videoType
            // 是否点赞
            video.setStart(videoStarService.starState(videoId, userId));
            // 是否收藏
            video.setFavorites(favoritesVideoService.favoritesState(videoId, userId));
            // 是否关注
            video.setFollow(followService.followState(video.getUserId(), userId));
        }
        return video;
    }

    @Override
    public List<HotVideo> listHotRank() { // 获取热门排行榜
        // 热门排行榜储存在redis中
        Set<ZSetOperations.TypedTuple<Object>> hotRankSet = redisTemplate.opsForZSet().rangeWithScores(RedisConstant.HOT_RANK, 0, -1);
        // 转化为list集合
        List<HotVideo> hotVideoList = new ArrayList<>();
        for (ZSetOperations.TypedTuple<Object> hotRank : hotRankSet) {
            try {
                // value中储存的是序列化的HotVideo对象
                HotVideo hotVideo = objectMapper.readValue(String.valueOf(hotRank.getValue()), HotVideo.class);
                hotVideo.setHot(hotRank.getScore());
                // 将热度格式化
                hotVideo.hotFormat();
                hotVideoList.add(hotVideo);
            } catch (Exception e) {
                throw new RuntimeException("listHotRank方法：" + e.getMessage());
            }
        }
        return hotVideoList;
    }

    @Override
    public List<Video> pushVideoSimilar(Video video) { // 根据视频标签推送相似视频
        List<String> buildLabel = video.buildLabel();
        ArrayList<String> labelList = new ArrayList<>();
        labelList.addAll(buildLabel); // 一个视频对应的标签可能比较少，添加两次，即一个标签推送两个视频
        labelList.addAll(buildLabel);
        // 根据标签推送视频
        Set<Long> videoIdSet = interestPushService.listVideoIdByLabels(labelList);
        List<Video> videoList = new ArrayList<>();
        if (!ObjectUtils.isEmpty(videoIdSet)) {
            videoList = this.listByIds(videoIdSet);
            setUserVoAndVideoType(videoList);
        }
        return videoList;
    }

    @Override
    public List<Video> listHotVideo() { // 推送热门视频，热门视频会记录3天内的视频
        // 获取当前时间
        Calendar calendar = Calendar.getInstance();
        int today = calendar.get(Calendar.DATE); // 如果当前时间为2024年11月2日，today等于2
        // 优先推送今天的视频
        HashMap<String, Integer> videoNum = new HashMap<>();
        videoNum.put(RedisConstant.HOT_VIDEO + today, 10); // 推送10条今天的视频
        videoNum.put(RedisConstant.HOT_VIDEO + (today - 1), 3); // 推送3条昨天的视频
        videoNum.put(RedisConstant.HOT_VIDEO + (today - 2), 2); // 推送2条前天的视频

        List<Object> objectList = redisTemplate.executePipelined((RedisCallback<?>) connection -> {
            videoNum.forEach((k, v) -> {
                // 从set中取出v个元素
                connection.sRandMember(k.getBytes(), v);
            });
            return null;
        });
        // 过滤null值，转换为Long，去重
        Set<Long> videoIdSet = objectList.stream().filter(o -> !ObjectUtils.isEmpty(o)).map(o -> Long.valueOf(o.toString())).collect(Collectors.toSet());
        List<Video> videoList = new ArrayList<>();
        if (!ObjectUtils.isEmpty(videoIdSet)) {
            videoList = this.listByIds(videoIdSet);
            setUserVoAndVideoType(videoList);
        }
        return videoList;
    }

    @Override
    public IPage<Video> listVideoByUserId(Long userId, BasePage basePage) { // 根据userId查询视频
        IPage<Video> videoPage = this.page(basePage.page(), new LambdaQueryWrapper<Video>().eq(Video::getUserId, userId));
        List<Video> videoList = videoPage.getRecords();
        setUserVoAndVideoType(videoList);
        return videoPage;
    }

    @Override
    public List<String> searchHistory(Long userId) { // 根据userId查询搜索记录，用户的搜索记录储存在redis中
        ArrayList<String> searchList = new ArrayList<>();
        if (userId != null) {
            String key = RedisConstant.USER_SEARCH_HISTORY + userId;
            Set<Object> objectSet = redisTemplate.opsForZSet().reverseRange(key, 0, -1);
            searchList.addAll(objectSet.stream().map(Object::toString).collect(Collectors.toList()));
        }
        return searchList;
    }

    @Override
    public void auditProcess(Video video) { // 放行视频
        // 放行后，修改video相关的数据
        video.setAuditStatus(AuditStatus.SUCCESS);
        video.setOpen(false);
        video.setMsg("人工审核放行");
        this.updateById(video);
        // 推入标签库
        interestPushService.pushSystemStockIn(video);
        // 添加分类库,用于后续随机推送分类视频
        interestPushService.pushSystemTypeStockIn(video);
        // 将视频推送至该视频博主的发件箱
        feedService.pusOutBoxFeed(video.getUserId(), video.getId(), video.getGmtCreated().getTime());
    }

    @Override
    public void violations(Long id) { // 下架视频
        Video video = getById(id);
        // 修改视频信息，true为私密，false为公开，默认为false
        video.setOpen(true);
        video.setMsg("该视频违反了乐刷视频的规则，已被下架私密");
        video.setAuditStatus(AuditStatus.PASS);
        // 删除分类库中的视频，与兴趣推送有关
        interestPushService.deleteSystemTypeStockIn(video);
        // 删除标签内的视频，与兴趣推送有关
        interestPushService.deleteSystemStockIn(video);
        // 获取视频发布者id
        Long userId = video.getUserId();
        // 删除当前用户发件箱中的视频，key为out:follow:feed:${userId}
        redisTemplate.opsForZSet().remove(RedisConstant.OUT_FOLLOW + userId, id);
        // 查询用户粉丝的id集合，降序排序
        List<Long> fansIds = followService.getFollowList(null, userId);
        // 删除当前用户收件箱中的视频
        feedService.deleteInBoxFeed(userId, Collections.singletonList(id));
        // 删除当前用户的发件箱中的视频以及粉丝下的收件箱中的视频
        feedService.deleteOutBoxFeed(userId, fansIds, id);
        Calendar calendar = Calendar.getInstance();
        int today = calendar.get(Calendar.DATE); // 当前日期时间
        Long videoId = video.getId();
        // 删除热门视频中的指定视频，key为hot:video:${today}
        redisTemplate.opsForSet().remove(RedisConstant.HOT_VIDEO + today, videoId);
        redisTemplate.opsForSet().remove(RedisConstant.HOT_VIDEO + (today - 1), videoId);
        redisTemplate.opsForSet().remove(RedisConstant.HOT_VIDEO + (today - 2), videoId);
        // 删除热门排行榜中的指定视频，key为hot:rank
        redisTemplate.opsForZSet().remove(RedisConstant.HOT_RANK, videoId);
        // 修改视频，update video set label_names = #{labelNames}, open = #{open}, msg = #{msg}, audit_status = #{auditStatus} where id = #{id}
        this.updateById(video);
    }

    private void setUserVoAndVideoType(List<Video> videoList) { // 为Video对象设置UserVo和videoType
        if (!ObjectUtils.isEmpty(videoList)) {
            // 从video中取出视频作者的用户id和视频url，视频url是文件的id
            HashSet<Long> userIdSet = new HashSet<>(); // 可能会推送同一个作者的不同视频，使用set存储起到了去重的作用
            ArrayList<Long> fileIdList = new ArrayList<>(); // 不会有重复视频
            for (Video video : videoList) {
                userIdSet.add(video.getUserId()); // 用户id
                fileIdList.add(video.getUrl()); // 视频url
            }
            List<User> userList = userService.listByIds(userIdSet);
            // key为用户id，value为User对象
            Map<Long, User> userMap = userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
            List<File> fileList = fileService.listByIds(fileIdList);
            // key为文件id，value为File对象
            Map<Long, File> fileMap = fileList.stream().collect(Collectors.toMap(File::getId, Function.identity()));
            for (Video video : videoList) {
                // 设置userVO
                User user = userMap.get(video.getUserId());
                UserVO userVO = new UserVO();
                BeanUtils.copyProperties(user, userVO); // 将user中的属性相同的数据复制到userVo中
                video.setUser(userVO);
                // 设置videoType
                File file = fileMap.get(video.getUrl());
                video.setVideoType(file.getFormat());
            }
        }
    }

    private void updateFavorites(Long videoId, String value) { // 修改视频收藏数
        LambdaUpdateWrapper<Video> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.setSql("favorites_count = favorites_count " + value + " 1").eq(Video::getId, videoId);
        this.update(updateWrapper);
    }

    private void updateStar(Long videoId, String value) { // 修改视频点赞数
        LambdaUpdateWrapper<Video> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.setSql("start_count = start_count " + value + " 1").eq(Video::getId, videoId);
        this.update(updateWrapper);
    }

    private void updateHistory(Long videoId) { // 修改视频浏览量，只增不减
        LambdaUpdateWrapper<Video> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.setSql("history_count = history_count + 1");
        updateWrapper.eq(Video::getId, videoId);
        this.update(updateWrapper);
    }
}
